package org.ccay.core.util;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.ccay.core.ioc.AnnotationMetadataDelegate;
import org.ccay.core.ioc.CcayApplicationContext;
import org.ccay.core.log.CcayLoggerFactory;
import org.ccay.core.log.ILogger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

/**
 * Annotation扫描器，可以扫描指定包下的、标识了特定annotation的类文件。
 * 
 * Created：2011-9-15
 * @author chaowangbang
 * @version:
 */
public class AnnotationMetadataScanner {

	private final ILogger logger = CcayLoggerFactory.getLogger(AnnotationMetadataScanner.class);

	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

	private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

	/** 元数据解析器工厂类 **/
	private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

	/** Include过滤器 **/
	private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();

	/** Exclude过滤器 **/
	private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

	/**
	 * 设置CcayApplicationContext
	 * 
	 * @param resourceLoader
	 */
	public void setCcayApplicationContext(CcayApplicationContext resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
	}

	/**
	 * Add an include type filter to the <i>end</i> of the inclusion list.
	 */
	public void addIncludeFilter(TypeFilter includeFilter) {
		this.includeFilters.add(includeFilter);
	}

	/**
	 * Add an exclude type filter to the <i>front</i> of the exclusion list.
	 */
	public void addExcludeFilter(TypeFilter excludeFilter) {
		this.excludeFilters.add(0, excludeFilter);
	}

	/**
	 * 扫描basePackages包下所有的类，返回标识了给定annotation的类列表。
	 * 
	 * @param basePackages
	 * @param annotation
	 * @return
	 */
	public Set<AnnotationMetadataDelegate> scan(String[] basePackages, Class<? extends Annotation> annotation) {
		this.addIncludeFilter(new AnnotationTypeFilter(annotation));

		Set<AnnotationMetadataDelegate> annotatedResources = new LinkedHashSet<AnnotationMetadataDelegate>();
		if(basePackages==null || basePackages.length==0) {
			return annotatedResources;
		}
		
		for (String basePackage : basePackages) {
			annotatedResources.addAll(findAnnotatedResources(basePackage, annotation.getName()));
		}
		return annotatedResources;
	}

	/**
	 * 扫描一个单独的包
	 * 
	 * @param basePackage
	 * @param annotationType
	 * @return
	 */
	private Set<AnnotationMetadataDelegate> findAnnotatedResources(String basePackage, String annotationType) {
		Set<AnnotationMetadataDelegate> annotatedResources = new LinkedHashSet<AnnotationMetadataDelegate>();

		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
			resolveBasePackage(basePackage) + "/" + this.resourcePattern;
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				AnnotationMetadataDelegate annotatedResource = findAnnotatedResource(resource, annotationType);
				if(annotatedResource != null) {
					annotatedResources.add(annotatedResource);
				}
			}
		} catch (IOException ioe) {
			logger.error(ioe);
		}

		return annotatedResources;
	}
	
	/**
	 * 解析单个资源文件
	 * 	
	 * 
	 * @since 2011-10-26
	 * @param resource
	 * @param annotationType
	 * @return
	 */
	private AnnotationMetadataDelegate findAnnotatedResource(Resource resource, String annotationType) {
		if(resource.isReadable()) {
			try {
				MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
				if(isAnnotatedResource(metadataReader)) {
					AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
					return new AnnotationMetadataDelegate(annotationType, annotationMetadata);
				}
			} catch (IOException ioe) {
				logger.error(ioe);
			}
		} else {
			logger.warn("Ignored because not readable: " + resource);
		}
		
		return null;
	}

	/**
	 * Resolve the specified base package into a pattern specification for
	 * the package search path.
	 * <p>The default implementation resolves placeholders against system properties,
	 * and converts a "."-based package path to a "/"-based resource path.
	 * 
	 * @param basePackage the base package as specified by the user
	 * @return the pattern specification to be used for package searching
	 */
	protected String resolveBasePackage(String basePackage) {
		return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
	}

	/**
	 * Determine whether the given class does not match any exclude filter
	 * and does match at least one include filter.
	 * 
	 * @param metadataReader the ASM ClassReader for the class
	 * @return whether the class qualifies as a candidate component
	 */
	protected boolean isAnnotatedResource(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return true;
			}
		}
		return false;
	}

}
